/*
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package commandhandlers;

import input.IRCCommand;
import interfaces.CalculableUtopiaEntity;
import interfaces.CommandHandler;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Collections;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import main.CommonMethods;
import main.Constants;
import main.UserCentral;

import org.apache.log4j.Logger;
import org.jibble.pircbot.Colors;

import output.IRCOutput;
import tools.InputType;
import utopiaintelpackage.database.SoMtoDB;
import utopiaintelpackage.database.SoTtoDB;
import utopiaintelpackage.som.SoM;
import utopiaintelpackage.sot.SoT;
import utopiaintelpackage.survey.Survey;
import botobjects.Province;
import botobjects.Race;
import botobjects.SoMArmy;
import botobjects.Target;
import database.DBConnectionFactory;

public class ChainHandler extends CommandHandler {
   private static ChainHandler INSTANCE = null;

   /**
    * Determines whether a certain player can actually break the target
    * 
    * @param hitter
    * @param target
    * @return
    */
   private static boolean canBreak(final Target hitter, final Target target) {
	  int hittersOff = 0;
	  int targetsDef = Integer.MAX_VALUE;

	  SoT sot = SoTtoDB.getSoT(CommonMethods.getProv(hitter.getName()));

	  if (sot != null) {
		 hittersOff = (int) (getPMO(sot) * 1.09);

		 SoT targetSoT = SoTtoDB.getSoT(target.getName());
		 SoM som = SoMtoDB.getSoM(target.getName());
		 if (targetSoT != null && som != null) {
			boolean hasArmyOut = false;
			for (SoMArmy army : som.getArmies()) {
			   if (army.getArmyType() == Constants.SOM_ARMY_OUT) {
				  hasArmyOut = true;
				  break;
			   }
			}
			if (hasArmyOut) {
			   targetsDef = (int) (getDefHome(targetSoT, som) * 1.073);
			}
		 }
		 if (targetsDef == Integer.MAX_VALUE && targetSoT != null) {
			targetsDef = targetSoT.getMd();
		 }
	  }
	  return hittersOff >= targetsDef;
   }

   private static int getPMO(final SoT sot) {
	  final Race race = Race.getRace(sot.getRace());
	  if (race == null)
		 return 0;
	  final Double ome = sot.getMo()
			* 1.0
			/ (sot.getOspecs() * race.getOspecstr() + sot.getElites() * race.getEliteostr() + sot.getSoldiers()*(sot.getRace().equals("Halfling")?2:1) + Math.min(
				  sot.getOspecs() + sot.getElites() + sot.getSoldiers(), sot.getHorses()));
	  final Double pmo = (sot.getOspecs() * race.getOspecstr() + sot.getElites() * race.getEliteostr() * race.getElitesoutratio()
			/ 100.0 + sot.getSoldiers()*(sot.getRace().equals("Halfling")?2:1) + Math.min(sot.getOspecs() + sot.getElites() + sot.getSoldiers(), sot.getHorses()) + Math
			.min((sot.getOspecs() + sot.getElites() + sot.getSoldiers()) / 5, sot.getPrisoners()) * 3) * ome;
	  return pmo.intValue();
   }

   private static int getDefHome(final SoT sot, final SoM som) {
	  final Race race = Race.getRace(sot.getRace());
	  if (race == null)
		 return Integer.MAX_VALUE;
	  int tw = 0;
	  if (sot.getRace().equals("Halfling") || sot.getRace().equals("Avian") || sot.getRace().equals("Faery") || sot.getRace().equals("Undead")) {
		 tw = sot.getPeasants() / 4;
	  }
	  final Double dme = sot.getMd() * 1.0
			/ (sot.getDspecs() * race.getDspecstr() + sot.getElites() * race.getElitedstr() + sot.getSoldiers()*(sot.getRace().equals("Halfling")?2:1) + tw);
	  int soldiersHome = sot.getSoldiers();
	  int elitesHome = sot.getElites();
	  for (SoMArmy army : som.getArmies()) {
		 if (army.getArmyType() == Constants.SOM_ARMY_OUT) {
			soldiersHome -= army.getSoldiers();
			elitesHome -= army.getElites();
		 }
	  }
	  final Double pmd = (sot.getDspecs() * race.getDspecstr() + elitesHome * race.getElitedstr() + soldiersHome*(sot.getRace().equals("Halfling")?2:1) + tw) * dme;
	  return pmd.intValue();
   }

   /**
    * Returns gang bang protection information for a province
    * 
    * @param prov
    * @return
    */
   private static String getGBProt(final String prov) {
	  String out = "";
	  PreparedStatement ps = null;
	  ResultSet rs = null;
	  try {
		 ps = DBConnectionFactory.getConnectionHandler().getConnection().prepareStatement("SELECT hit FROM sot WHERE name = ?");
		 ps.setString(1, prov);
		 rs = ps.executeQuery();

		 if (rs.next()) {
			out = rs.getString("hit");
		 }
	  } catch (final SQLException e) {
		 Logger.getLogger("Error").error("", e);
	  } finally {
		 try {
			rs.close();
		 } catch (final Exception e) {
		 }
		 try {
			ps.close();
		 } catch (final Exception e) {
		 }
	  }
	  return out;
   }

   /**
    * Returns the effect of any Guard Stations the target might have
    * 
    * @param province
    * @return
    */
   @SuppressWarnings("serial")
   private static Double getGSEffect(final String province) {
	  CalculableUtopiaEntity calculable = UtopiaHandler.loadBuildings().get("gs");
	  SoT sot = new Province(province, "", "", "", "", 0, 0).getSot();
	  Survey survey = new Province(province, "", "", "", "", 0, 0).getSurvey();
	  if (sot == null || survey == null)
		 return 1.0;
	  final String gs = String.valueOf(survey.getBuilding("Guard Stations"));
	  final String be = sot.getBe();
	  final String[] formula = calculable.getFormula(new IRCCommand(0, "", "") {
		 {
			add(gs);
			add(be);
		 }
	  });
	  if (formula == null)
		 return 1.0;

	  CalculationHandler calc = CalculationHandler.getInstance();
	  String temp = "1.0";
	  for (final String s : formula) {
		 temp = calc.calc(s);
	  }
	  String[] result = temp.split(" ");
	  return 1.0 - Double.parseDouble(result[result.length - 1]);
   }

   public static final ChainHandler getInstance() {
	  if (INSTANCE == null) {
		 INSTANCE = new ChainHandler();
	  }
	  return INSTANCE;
   }

   public static final ChainHandler newInstance() {
	  INSTANCE = null;
	  return getInstance();
   }

   /**
    * Removes the targets added to the target list by the setupmaxgains function
    * 
    * @return
    */
   private static List<String> removeMaxGainWave() {
	  List<String> out = new LinkedList<String>();
	  PreparedStatement ps = null;
	  try {
		 ps = DBConnectionFactory.getConnectionHandler().getConnection()
			   .prepareStatement("DELETE FROM target WHERE manualadd = false");
		 if (ps.executeUpdate() > 0) {
			out.add("Max gain targets removed");
		 }
	  } catch (final SQLException e) {
		 Logger.getLogger("Error").error("", e);
	  } finally {
		 try {
			ps.close();
		 } catch (final Exception e) {
		 }
	  }
	  return out;
   }

   /**
    * Adds a hitter to a target, with the specified position in the list of hitters
    * 
    * @param target
    * @param hitter
    * @param place
    * @return
    */
   public static boolean setHitter(final String target, final String hitter, final int place) {
	  boolean out = false;
	  PreparedStatement ps = null;
	  ResultSet rs = null;
	  try {
		 ps = DBConnectionFactory.getConnectionHandler().getConnection().prepareStatement("SELECT * FROM target WHERE name = ?");
		 ps.setString(1, target);
		 rs = ps.executeQuery();

		 if (rs.next()) {
			String newHitList = "";
			String[] split = rs.getString("message").split(" ");
			if (place < 1) {
			   for (String s : split) {
				  if (!hitter.equals(Colors.removeFormattingAndColors(s))) {
					 newHitList += s + " ";
				  }
			   }
			} else if (place > split.length) {
			   newHitList = rs.getString("message") + " " + hitter;
			} else {
			   for (int i = 0; i < split.length; ++i) {
				  if (i + 1 == place) {
					 newHitList += hitter + " ";
				  }
				  newHitList += split[i] + " ";
			   }
			}

			ps = DBConnectionFactory.getConnectionHandler().getConnection()
				  .prepareStatement("UPDATE target SET message = ? WHERE name = ?");
			ps.setString(1, newHitList.trim());
			ps.setString(2, target);
			if (ps.executeUpdate() == 1) {
			   out = true;
			}
		 }
	  } catch (final SQLException e) {
		 Logger.getLogger("Error").error("", e);
	  } finally {
		 try {
			rs.close();
		 } catch (final Exception e) {
		 }
		 try {
			ps.close();
		 } catch (final Exception e) {
		 }
	  }
	  return out;
   }

   /**
    * Sets up a max gain wave, checking for targets for everyone and matching to those that will give the best gains
    */
   public static boolean setupMaxGainWave(final String kd, final String priority, final boolean isWar) {
	  boolean out = false;
	  PreparedStatement ps = null;
	  ResultSet rs = null;
	  LinkedList<Target> selfKD = new LinkedList<Target>();
	  LinkedList<Target> enemyKD = new LinkedList<Target>();
	  try {
		 ps = DBConnectionFactory.getConnectionHandler().getConnection()
			   .prepareStatement("SELECT * FROM " + "kdprovince,province WHERE province.name = kdprovince.province");
		 rs = ps.executeQuery();

		 while (rs.next()) {
			selfKD.add(new Target("", rs.getInt("nw"), rs.getInt("land"), rs.getString("mainnick"), rs.getString("kdloc"), "",
				  false, GregorianCalendar.getInstance().getTimeInMillis()));
		 }

		 ps = DBConnectionFactory.getConnectionHandler().getConnection()
			   .prepareStatement("SELECT * FROM province WHERE kdloc = ?");
		 ps.setString(1, kd);
		 rs = ps.executeQuery();

		 while (rs.next()) {
			enemyKD.add(new Target("", rs.getInt("nw"), rs.getInt("land"), rs.getString("name"), rs.getString("kdloc"),
				  getGBProt(rs.getString("name")), isWar, GregorianCalendar.getInstance().getTimeInMillis()));
		 }
	  } catch (final SQLException e) {
		 Logger.getLogger("Error").error("", e);
	  } finally {
		 try {
			rs.close();
		 } catch (final Exception e) {
		 }
		 try {
			ps.close();
		 } catch (final Exception e) {
		 }
	  }

	  if ("small".equals(priority)) {
		 Collections.sort(selfKD);
	  } else if ("big".equals(priority)) {
		 Collections.sort(selfKD);
		 Collections.reverse(selfKD);
	  } else if ("mid".equals(priority)) {
		 selfKD = specialSort(selfKD);
	  }

	  Collections.sort(enemyKD);
	  Collections.reverse(enemyKD);

	  Map<Integer, LinkedList<String>> hitList = new HashMap<Integer, LinkedList<String>>();
	  for (Target hitter : selfKD) {
		 int bestTarget = -1;
		 int gain = 0;
		 for (Target target : enemyKD) {
			Double tempgain = CalculationHandler.calcGains(target.getNw(), hitter.getNw(), target.getLand(), "tm").intValue()
				  * target.getGbprot() * getGSEffect(target.getName());
			if (tempgain.intValue() > gain && tempgain / hitter.getLand() > 0.05 && canBreak(hitter, target)) {
			   bestTarget = enemyKD.indexOf(target);
			   gain = tempgain.intValue();
			}
		 }
		 if (bestTarget != -1) {
			if (hitList.containsKey(bestTarget)) {
			   hitList.get(bestTarget).add(hitter.getName());
			} else {
			   LinkedList<String> hitters = new LinkedList<String>();
			   hitters.add(hitter.getName());
			   hitList.put(bestTarget, hitters);
			}
			enemyKD.get(bestTarget).adjustLand(gain);
		 }
	  }
	  try {
		 removeMaxGainWave();
		 Long currentTime = GregorianCalendar.getInstance().getTimeInMillis();
		 for (Entry<Integer, LinkedList<String>> e : hitList.entrySet()) {
			ps = DBConnectionFactory.getConnectionHandler().getConnection()
				  .prepareStatement("INSERT INTO target VALUES(?,?,?,false,?)");
			ps.setString(1, enemyKD.get(e.getKey()).getName());
			ps.setString(2, kd);
			String hitters = "";
			for (String s : e.getValue()) {
			   hitters += s + " ";
			}
			ps.setString(3, hitters.trim());
			ps.setTimestamp(4, new Timestamp(currentTime));
			ps.executeUpdate();
		 }
		 out = true;
	  } catch (final SQLException e) {
		 Logger.getLogger("Error").error("", e);
	  } finally {
		 try {
			ps.close();
		 } catch (final Exception e) {
		 }
	  }

	  return out;
   }

   /**
    * A sort that works from the middle out, meaning the provinces in each step the province in the middle of the list is moved to
    * the top of the sorted list
    * 
    * @param in
    * @return
    */
   private static LinkedList<Target> specialSort(final LinkedList<Target> in) {
	  LinkedList<Target> sorted = new LinkedList<Target>();
	  boolean everyOther = true;
	  while (!in.isEmpty()) {
		 if (everyOther) {
			sorted.addLast(in.removeLast());
			everyOther = false;
		 } else {
			sorted.addLast(in.removeFirst());
			everyOther = true;
		 }
	  }
	  return sorted;
   }

   /**
    * Updates the target list when someone hits
    * 
    * @param target
    * @param hitter
    */
   public static void updateTargetList(final String target, final String hitter) {
	  PreparedStatement ps = null;
	  ResultSet rs = null;
	  try {
		 ps = DBConnectionFactory.getConnectionHandler().getConnection()
			   .prepareStatement("SELECT * FROM target WHERE name = ? AND message LIKE ?");
		 ps.setString(1, target);
		 ps.setString(2, "%" + hitter + "%");
		 rs = ps.executeQuery();

		 if (rs.next()) {
			ps = DBConnectionFactory.getConnectionHandler().getConnection()
				  .prepareStatement("UPDATE target SET message = ? WHERE name = ?");
			ps.setString(1, rs.getString("message").replace(hitter, Colors.DARK_GREEN + hitter + Colors.NORMAL));
			ps.executeUpdate();
		 }
	  } catch (final SQLException e) {
		 Logger.getLogger("Error").error("", e);
	  } finally {
		 try {
			rs.close();
		 } catch (final Exception e) {
		 }
		 try {
			ps.close();
		 } catch (final Exception e) {
		 }
	  }
   }

   private ChainHandler() {
   }

   @Override
   public void handle(final IRCCommand command, final IRCOutput output) {
   }

   public void handleAddhitter(final IRCCommand command, final IRCOutput out) {
	  if (command.size() >= 4 && command.isInt(2)) {
		 if (UserCentral.getInstance().onlineUserIsAdmin(command.getSender(), command.getChannel())
			   && !"".equals(CommonMethods.getUserFromDB(command.get(1), false))) {
			setHitter(command.getPartialMessage(3, command.size()), CommonMethods.getUserFromDB(command.get(1), false), -1);
			if (setHitter(command.getPartialMessage(3, command.size()), CommonMethods.getUserFromDB(command.get(1), false),
				  Integer.parseInt(command.get(2)))) {
			   out.addOutput("Hitter added");
			}
		 }
	  }
   }

   public void handleAddtarget(final IRCCommand command, final IRCOutput out) {
	  if (UserCentral.getInstance().onlineUserIsAdmin(command.getSender(), command.getChannel()) && command.size() > 2) {
		 String msg = command.getFullMessage();
		 if (!msg.matches(".*\\([0-9]{1,2}(:)[0-9]{1,2}\\) .*"))
			return;
		 String prov = msg.substring(0, msg.indexOf('(')).trim();
		 String loc = msg.substring(msg.indexOf('('), msg.indexOf(')') + 1).trim();
		 msg = msg.substring(msg.indexOf(')') + 1);
		 if (new Target(msg, 0, 0, prov, loc, "", false, GregorianCalendar.getInstance().getTimeInMillis()).saveToDB()) {
			out.addOutput("Target added");
		 }
	  }
   }

   public void handleMovehitter(final IRCCommand command, final IRCOutput out) {
	  if (command.size() >= 4 && command.isInt(2)) {
		 if (UserCentral.getInstance().onlineUserIsAdmin(command.getSender(), command.getChannel())
			   && !"".equals(CommonMethods.getUserFromDB(command.get(1), false))) {
			setHitter(command.getPartialMessage(3, command.size()), CommonMethods.getUserFromDB(command.get(1), false), -1);
			if (setHitter(command.getPartialMessage(3, command.size()), CommonMethods.getUserFromDB(command.get(1), false),
				  Integer.parseInt(command.get(2)))) {
			   out.addOutput("Hitter moved");
			}
		 }
	  }
   }

   public void handleRemovehitter(final IRCCommand command, final IRCOutput out) {
	  if (command.size() >= 3) {
		 if (UserCentral.getInstance().onlineUserIsAdmin(command.getSender(), command.getChannel())
			   && !"".equals(CommonMethods.getUserFromDB(command.get(1), false)))
			if (setHitter(command.getPartialMessage(2, command.size()), CommonMethods.getUserFromDB(command.get(1), false), -1)) {
			   out.addOutput("Hitter removed");
			}
	  }
   }

   public void handleRemovemaxgain(final IRCCommand command, final IRCOutput out) {
	  if (UserCentral.getInstance().onlineUserIsAdmin(command.getSender(), command.getChannel())) {
		 out.addOutput(removeMaxGainWave());
	  }
   }

   public void handleRemovetarget(final IRCCommand command, final IRCOutput out) {
	  if (UserCentral.getInstance().onlineUserIsAdmin(command.getSender(), command.getChannel()) && command.size() > 1) {
		 String prov = command.getFullMessage();
		 prov = prov.replaceAll("\\([0-9]{1,2}(:)[0-9]{1,2}\\)", "");
		 if (Target.deleteFromDB(prov)) {
			out.addOutput("Target removed");
		 }
	  }
   }

   public void handleSetupmaxgain(final IRCCommand command, final IRCOutput out) {
	  if (UserCentral.getInstance().onlineUserIsAdmin(command.getSender(), command.getChannel())) {
		 if (command.size() == 2 && command.validate(1, InputType.KDLOC) && setupMaxGainWave(command.get(1), "small", false)) {
			out.addOutput(Colors.RED + "Max gain wave setup. Hitlist in !targets");
		 } else if (command.size() == 3
			   && command.validate(1, InputType.KDLOC)
			   && ("small".equalsIgnoreCase(command.get(2)) || "mid".equalsIgnoreCase(command.get(2)) || "big"
					 .equalsIgnoreCase(command.get(2))) && setupMaxGainWave(command.get(1), command.get(2).toLowerCase(), false)) {
			out.addOutput(Colors.RED + "Max gain wave setup. Hitlist in !targets");
		 } else if (command.size() == 3 && command.validate(1, InputType.KDLOC) && command.get(2).equalsIgnoreCase("war")
			   && setupMaxGainWave(command.get(1), "small", true)) {
			out.addOutput(Colors.RED + "Max gain wave setup. Hitlist in !targets");
		 } else if (command.size() == 4
			   && command.validate(1, InputType.KDLOC)
			   && ("small".equalsIgnoreCase(command.get(2)) || "mid".equalsIgnoreCase(command.get(2)) || "big"
					 .equalsIgnoreCase(command.get(2))) && command.get(3).equalsIgnoreCase("war")
			   && setupMaxGainWave(command.get(1), command.get(2).toLowerCase(), true)) {
			out.addOutput(Colors.RED + "Max gain wave setup. Hitlist in !targets");
		 }
	  }
   }

   public void handleTargets(final IRCCommand command, final IRCOutput out) {
	  if (command.size() == 1) {
		 for (Target target : Target.getTargets()) {
			out.addOutput(target.getIRCOutput(0));
		 }
	  } else if (!"".equals(CommonMethods.getUserFromDB(command.get(1), false))) {
		 for (Target target : Target.getTargets()) {
			if (target.getMessage().matches(".*" + command.get(1) + ".*")) {
			   out.addOutput(target.getIRCOutput(0));
			}
		 }
	  }
   }
}
